package codeoffloading.profiling;

import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.HashMap;

import utilities.BatteryLevel;
import utilities.Constants;

public class Profiler {
	
	private String className;
	
	private HashMap<String, MethodProfiler> methods;
	
	
/**
 * change for keep track of the timeout
 */
	
	
	public Profiler(Class instance){
		className = instance.getCanonicalName();
		methods = new HashMap<String, MethodProfiler>();
		for(Method m: instance.getMethods()){
			methods.put(m.getName(), new MethodProfiler(m.getName()));
		}
	}
	
	public void methodCall(String methodName){
		methods.get(methodName).call();
	}
	
	public void methodCallEnd(String methodName){
		methods.get(methodName).callEnd();
	}
	
	public long getMethodWaitTime(String methodName){
		return methods.get(methodName).getWaitTime();
	}
	
	
	/**
	 * Easy decision based on single method statistics
		to be update with more methods
	 */
	public boolean offloadDecision(String methodName){
		MethodProfiler mp = methods.get(methodName);
		
		/**
		 * ADD WEIGHTED PARAMETERS
		 */
		if(mp.getE() > mp.getERemote() && mp.getT() > mp.getTRemote()) return true;
		else return false;
		
	}
	
	public class MethodProfiler{
		
		/*
		 * Local Profiler
		 * E = energy execute the method locally
		 * T = mean time executing the method locally
		 * 
		 * Tr = time executing the method remotely
		 * Er = energy consumed in executing the method remotely (state transferring and receiving
		 * 
		 * Uniformly bias setted
		 */
		private double E = 0.01;
		private long nE = 1; //samples of elements collected in E
		
		private long T = 1;
		private long nT = 1; //samples samples collected in T
		
		
		private double Er = 0.01;
		private long nEr = 1; //samples on remote execution
		
		private long Tr = 1;
		private long nTr = 1; //samples on uniform execution
		
		 
		private long timeTM = 0;
		private float batteryUsg = 0;
		
		//default wait
		private long waitTime = Constants.DEF_WIAT;
		
		private String methodName;
		private boolean isRunning = false;
		
		public MethodProfiler(String methodName){
			
			this.methodName = methodName;
		}
		
		public void call(){
			timeTM = System.nanoTime();
			batteryUsg = BatteryLevel.getBatteryLevel();
			isRunning = true;
		}
		public void callEnd(){
			long overallTime = timeTM - System.nanoTime();
			float battUs = batteryUsg - BatteryLevel.getBatteryLevel();
			T = (T)+overallTime;
			nT++;
			
			if(battUs == 0) battUs = 0.01f;
			
			E = (E)+battUs;
			nE++;
			
			//UPDATE WHAITING TIME
			if(T/1000 < 1) this.waitTime = Constants.MIN_WHAIT;
			else this.waitTime = T/1000+Constants.WAIT_THRESHOLD;
			
			isRunning = false;
		}
		
		public void remoteCall(){
			timeTM = System.nanoTime();
			isRunning = true;
		}
		
		public void remoteCallEnd(){
			long overallTime = timeTM - System.nanoTime();
			Tr = (Tr)+overallTime;
			nTr++;
			
			float battUs = batteryUsg - BatteryLevel.getBatteryLevel();
			

			if(battUs == 0) battUs = 0.01f;
			
			Er = (Er)+battUs;
			nEr++;
			
			isRunning = false;
		}
		
		
		public double getE(){  
			return E/nE;
		}
		
		
		public double getERemote(){
			return Er / nEr;
		}
		
		
		public long getT(){ return T/nT; }
		public long getTRemote(){ return Tr / nTr; }
		public boolean isRunning(){ return isRunning; }
		public long getWaitTime(){ return this.waitTime; }
		
	}
	

}
